﻿using System;
using System.Collections.Generic;
using System.Text;
using FlexminAPI;
using System.Windows.Forms;
using System.IO;
using System.Threading;
using System.Net.Sockets;
using System.Net;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Security.Cryptography;
using Microsoft.Win32;
using System.Diagnostics;
using System.Security;
using FlexLib;
using System.ServiceProcess;
using FlexLib.Plugins;

namespace FlexminHost
{
	internal class FlexminService : ServiceBase
	{
		private ICollection<Plugin> plugins;
		private byte[] passwordHash = null;

		EventLog eventLog = new EventLog("Flexmin");

		private bool CheckPassword(string password)
		{
			if (passwordHash == null)
				return true;

			SHA512 sha512 = new SHA512Managed();

			byte[] passBytes = Encoding.UTF8.GetBytes(password);
			byte[] hash = sha512.ComputeHash(passBytes);
			if (hash.Length != passwordHash.Length)
				return false;

			for (int iByte = 0; iByte < hash.Length; iByte++)
			{
				if (hash[iByte] != passwordHash[iByte])
					return false;
			}

			return true;
		}

		private bool ChangePassword(string newPass)
		{
			SHA512 sha512 = new SHA512Managed();

			byte[] passBytes = Encoding.UTF8.GetBytes(newPass);
			passwordHash = sha512.ComputeHash(passBytes);
			WriteHash();

			return true;
		}

		public void ReadHash()
		{
			try
			{
				RegistryKey key = Registry.LocalMachine.OpenSubKey("Software").OpenSubKey("Flexmin");
				byte[] hash = key.GetValue("Hash") as byte[];
				passwordHash = hash;
			}
			catch
			{
				passwordHash = null;
			}
		}

		private void WriteHash()
		{
			if (passwordHash == null)
				return;

			RegistryKey key, nextKey;
			try
			{
				key = Registry.LocalMachine.OpenSubKey("Software", true);
				if (key == null)
					throw new NullReferenceException();
			}
			catch
			{
				try
				{
					key = Registry.LocalMachine.CreateSubKey("Software");
					if (key == null)
						throw new NullReferenceException();
				}
				catch
				{
					return;
				}
			}

			try
			{
				nextKey = key.OpenSubKey("Flexmin", true);
				if (nextKey == null)
					throw new NullReferenceException();
			}
			catch
			{
				try
				{
					nextKey = key.CreateSubKey("Flexmin");
					if (nextKey == null)
						throw new NullReferenceException();
				}
				catch
				{
					return;
				}
			}

			key = nextKey;

			try
			{
				key.SetValue("Hash", passwordHash);
			}
			catch
			{
				return;
			}
		}

		private void InitPlugins()
		{
			TcpChannel chan = new TcpChannel(NetHelp.SERVER_REMOTING_PORT); // Need to hadle for errors!!!!
			try
			{
				ChannelServices.RegisterChannel(chan, false);
				//eventLog.WriteEntry("Open port " + NetHelp.SERVER_REMOTING_PORT.ToString(), EventLogEntryType.Information);
			}
			catch (SecurityException)
			{
				//if (this.runMode == RunMode.Application)
					MessageBox.Show("Security error!", "ERROR!", MessageBoxButtons.OK, MessageBoxIcon.Error);
				//else
					///eventLog.WriteEntry("Security error!", EventLogEntryType.Error);
				return;
			}
			catch (RemotingException)
			{
				//if (this.runMode == RunMode.Application)
					MessageBox.Show("Remoting error!", "ERROR!", MessageBoxButtons.OK, MessageBoxIcon.Error);
				//else
					//eventLog.WriteEntry("Remoting error!", EventLogEntryType.Error);
				return;
			}

			plugins = PluginManager.HostInstance.Plugins;
			PluginManager.HostInstance.Change += new EventHandler(HostInstance_Change);

			// TODO: Быдлокод detected!
			foreach (Plugin plugin in plugins)
			{
				if (plugin.Slave != null)
				{
					try
					{
						RemotingConfiguration.RegisterWellKnownServiceType(
								  plugin.SlaveType,
								  plugin.Info.RemotingName,
								  WellKnownObjectMode.Singleton);
					}
					catch (SecurityException)
					{

					}
				}
			}
		}

		void HostInstance_Change(object sender, EventArgs e)
		{
			plugins = PluginManager.HostInstance.Plugins;
		}

		private void Listen(object _listener)
		{
			Socket listener = _listener as Socket;	
			while (true)
			{
				Socket client = null;
				try
				{
					client = listener.Accept();					
				}
				catch (SocketException ex)
				{
					return;
				}
				SocketReader reader = new SocketReader(client);
				SocketWriter writer = new SocketWriter(client);				

				string password = reader.ReadString();
				if (CheckPassword(password))
				{
					writer.WriteInt(NetHelp.PASSWORD_OK);
				}
				else
				{
					writer.WriteInt(NetHelp.PASSWORD_WRONG);
					writer.WriteInt(NetHelp.PASSWORD_OK);
					writer.Close();
					continue;
				}

				try
				{
					int action = reader.ReadInt();

					switch (action)
					{
						case NetHelp.ACTION_LIST_PLUGINS:
							PluginTransport.ListPlugins(plugins,reader, writer);
							break;
						case NetHelp.ACTION_CHANGE_PASSWORD:
							ChangePasswordAction(reader, writer);
							break;
						case NetHelp.ACTION_UPLOAD_PLUGIN:
							PluginTransport.UploadPlugins(reader, writer);
							break;
						case NetHelp.ACTION_DOWNLOAD_PLUGIN:
							PluginTransport.DownloadPlugins(reader, writer);
							break;
						case NetHelp.ACTION_DELETE_PLUGIN:
							PluginManager.UninstallPlugin(reader, writer);
							break;
					}
				}
				catch
				{
					// TODO: Error handling
				}

				reader.Close();
			}
		}

		private void OnContMenuExit(object sender, EventArgs e)
		{
			ReadHash();
			if (passwordHash == null)
			{
				Application.Exit();
				return;
			}

			PasswordInputForm piForm = new PasswordInputForm(passwordHash);
			piForm.ShowDialog();

			if (!piForm.AllowFunction)
				return;
			Application.Exit();
			return;
		}

		private RunMode runMode;

		public FlexminService(RunMode mode)
		{
			runMode = mode;
			ReadHash();
		}

		private Socket listener;
		private Thread listenThread;
		private NotifyIcon trayIcon;

		public void AppplicationStart()
		{
			eventLog.Source = "FlexminSource";

			ContextMenuStrip con_menu = new ContextMenuStrip();
			con_menu.Items.Add("Exit");

			con_menu.Items[0].Click += new EventHandler(OnContMenuExit);

			trayIcon = new NotifyIcon();
			trayIcon.Icon = Properties.Resource1.SmallIcon;
			trayIcon.Text = "Flexmin Service";
			trayIcon.Visible = true;
			trayIcon.ContextMenuStrip = con_menu;

			InitPlugins();

			listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
			listener.Bind(new IPEndPoint(IPAddress.Any, NetHelp.SERVER_TCP_PORT));

			listener.Listen(0);

			listenThread = new Thread(new ParameterizedThreadStart(Listen));
			listenThread.Start(listener);
		}

		protected override void OnStart(string[] args)
		{
			eventLog.Source = "FlexminSource";

			InitPlugins();

			listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
			listener.Bind(new IPEndPoint(IPAddress.Any, NetHelp.SERVER_TCP_PORT));

			listener.Listen(0);

			listenThread = new Thread(new ParameterizedThreadStart(Listen));
			listenThread.Start(listener);
		
			base.OnStart(args);
		}

		public void ApplicationStop()
		{
			listener.Close();
			listenThread.Abort();
			
			trayIcon.Visible = false;
		}

		protected override void OnStop()
		{
			listener.Close();
			listenThread.Abort();
			
			base.OnStop();
		}

		void ChangePasswordAction(SocketReader reader, SocketWriter writer)
		{
			try
			{
				string newPassword = reader.ReadString();

				ChangePassword(newPassword);
			}
			catch
			{
				// TODO: Error handling
			}
		}

		private void InitializeComponent()
		{
			// 
			// FlexminService
			// 
			this.ServiceName = "Flexmin";
		}
	}
}
